package com.jackstraw.jack_base.datasource.common.entity;

import com.jackstraw.jack_base.datasource.common.contextholder.DataSourceContextHolder;
import com.jackstraw.jack_base.datasource.common.entity.enums.DataSourceTypeEnum;
import com.jackstraw.jack_base.util.ObjectUtil;
import com.jackstraw.jack_base.util.SpringUtil;
import com.jackstraw.jack_base.util.StringUtil;
import com.jackstraw.jack_base.util.entity.BaseEntity;
import com.jackstraw.jack_base.util.exception.BadConfigException;
import com.jackstraw.jack_base.util.exception.BadException;
import com.jackstraw.jack_base.util.exception.enums.ExceptionEnum;
import com.jackstraw.jack_base.util.tenant.contextholder.TenantContextHolder;
import com.jackstraw.jack_base.util.tenant.entity.Tenant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.util.HashMap;
import java.util.Map;

/**
 * 路由类
 *
 * @param <PE>
 * @param <T>
 * @param <P>
 * @param <DSR>
 */
@Slf4j
public class DataSourceRouting<PE extends AbstractPropertiesEntity<PE>,
        T extends AbstractTemplate<T, PE>,
        P extends Properties<PE>,
        DSR extends DataSourceRouting<PE, T, P, DSR>>
        extends BaseEntity<DSR> implements InitializingBean, DisposableBean {


    /**
     * 配置文件
     */
    private P defaultProperties;

    /**
     * 默认租户名称
     */
    private String defaultTenantName;
    /**
     * 默认数据源名称
     */
    private String defaultDataSourcetName;

    /**
     * 租户对应的默认数据源主名称
     */
    private Map<String, String> defaultDataSourceTenantNameWiteMainNameMap = new HashMap<>();

    /**
     * 默认提供服务类
     */
    private T defaultTargetTemplate;

    /**
     * 租户对数据源名称对服务类
     */
    private Map<String, Map<String, T>> defaultTargetTemplateTenantMap = new HashMap<>();


    /**
     * 租户对应默认服务类
     */
    private Map<String, T> defaultTenantTemplateMap = new HashMap<>();


    /**
     * 默认提供服务的配置文件
     */
    private PE defaultTargetPropertiesEntity;

    /**
     * 租户对数据源名称对配置文件
     */
    private Map<String, Map<String, PE>> defaultTargetPropertiesEntityTenantMap = new HashMap<>();


    /**
     * 名称对服务类
     */
    private Map<String, T> defaultTargetTemplateMap = new HashMap<>();


    /**
     * 配置文件
     */
    private P resolvedProperties;

    /**
     * 默认租户名称
     */
    private String resolvedTenantName;
    /**
     * 默认数据源名称
     */
    private String resolvedDataSourcetName;

    /**
     * 默认数据源主名称
     */
    private Map<String, String> resolvedDataSourceTenantNameWiteMainNameMap;


    /**
     * 默认提供服务类
     */
    private T resolvedTemplate;

    /**
     * 租户对数据源名称对服务类
     */
    private Map<String, Map<String, T>> resolvedTemplateTenantMap;

    /**
     * 租户对应默认数据源名称
     */
    private Map<String, T> resolvedTenantTemplateMap;

    /**
     * 默认提供服务的配置文件
     */
    private PE resolvedPropertiesEntity;

    /**
     * 租户对数据源名称对配置文件
     */
    private Map<String, Map<String, PE>> resolvedPropertiesEntityTenantMap;

    /**
     * 名称对服务类
     */
    private Map<String, T> resolvedTemplateMap;

    private P getDefaultProperties() {
        return defaultProperties;
    }

    public void setDefaultProperties(P defaultProperties) {
        this.defaultProperties = defaultProperties;
    }

    private String getDefaultTenantName() {
        return defaultTenantName;
    }

    public void setDefaultTenantName(String defaultTenantName) {
        this.defaultTenantName = defaultTenantName;
    }

    private String getDefaultDataSourcetName() {
        return defaultDataSourcetName;
    }

    public void setDefaultDataSourcetName(String defaultDataSourcetName) {
        this.defaultDataSourcetName = defaultDataSourcetName;
    }

    private Map<String, String> getDefaultDataSourceTenantNameWiteMainNameMap() {
        return defaultDataSourceTenantNameWiteMainNameMap;
    }

    public void setDefaultDataSourceTenantNameWiteMainNameMap(Map<String, String> defaultDataSourceTenantNameWiteMainNameMap) {
        this.defaultDataSourceTenantNameWiteMainNameMap = defaultDataSourceTenantNameWiteMainNameMap;
    }

    private T getDefaultTargetTemplate() {
        return defaultTargetTemplate;
    }

    public void setDefaultTargetTemplate(T defaultTargetTemplate) {
        this.defaultTargetTemplate = defaultTargetTemplate;
    }

    private Map<String, T> getDefaultTenantTemplateMap() {
        return defaultTenantTemplateMap;
    }

    public void setDefaultTenantTemplateMap(Map<String, T> defaultTenantTemplateMap) {
        this.defaultTenantTemplateMap = defaultTenantTemplateMap;
    }

    private Map<String, Map<String, T>> getDefaultTargetTemplateTenantMap() {
        return defaultTargetTemplateTenantMap;
    }

    public void setDefaultTargetTemplateTenantMap(Map<String, Map<String, T>> defaultTargetTemplateTenantMap) {
        this.defaultTargetTemplateTenantMap = defaultTargetTemplateTenantMap;
    }

    private PE getDefaultTargetPropertiesEntity() {
        return defaultTargetPropertiesEntity;
    }

    public void setDefaultTargetPropertiesEntity(PE defaultTargetPropertiesEntity) {
        this.defaultTargetPropertiesEntity = defaultTargetPropertiesEntity;
    }

    private Map<String, Map<String, PE>> getDefaultTargetPropertiesEntityTenantMap() {
        return defaultTargetPropertiesEntityTenantMap;
    }

    public void setDefaultTargetPropertiesEntityTenantMap(Map<String, Map<String, PE>> defaultTargetPropertiesEntityTenantMap) {
        this.defaultTargetPropertiesEntityTenantMap = defaultTargetPropertiesEntityTenantMap;
    }

    private Map<String, T> getDefaultTargetTemplateMap() {
        return defaultTargetTemplateMap;
    }

    private void setDefaultTargetTemplateMap(Map<String, T> defaultTargetTemplateMap) {
        this.defaultTargetTemplateMap = defaultTargetTemplateMap;
    }

    public P getResolvedProperties() {
        return resolvedProperties;
    }

    private void setResolvedProperties(P resolvedProperties) {
        this.resolvedProperties = resolvedProperties;
    }

    public String getResolvedTenantName() {
        return resolvedTenantName;
    }

    private void setResolvedTenantName(String resolvedTenantName) {
        this.resolvedTenantName = resolvedTenantName;
    }

    public String getResolvedDataSourcetName() {
        return resolvedDataSourcetName;
    }

    private void setResolvedDataSourcetName(String resolvedDataSourcetName) {
        this.resolvedDataSourcetName = resolvedDataSourcetName;
    }

    public Map<String, String> getResolvedDataSourceTenantNameWiteMainNameMap() {
        return resolvedDataSourceTenantNameWiteMainNameMap;
    }

    private void setResolvedDataSourceTenantNameWiteMainNameMap(Map<String, String> resolvedDataSourceTenantNameWiteMainNameMap) {
        this.resolvedDataSourceTenantNameWiteMainNameMap = resolvedDataSourceTenantNameWiteMainNameMap;
    }

    public T getResolvedTemplate() {
        return resolvedTemplate;
    }

    private void setResolvedTemplate(T resolvedTemplate) {
        this.resolvedTemplate = resolvedTemplate;
    }

    public Map<String, Map<String, T>> getResolvedTemplateTenantMap() {
        return resolvedTemplateTenantMap;
    }

    private void setResolvedTemplateTenantMap(Map<String, Map<String, T>> resolvedTemplateTenantMap) {
        this.resolvedTemplateTenantMap = resolvedTemplateTenantMap;
    }

    public Map<String, T> getResolvedTenantTemplateMap() {
        return resolvedTenantTemplateMap;
    }

    private void setResolvedTenantTemplateMap(Map<String, T> resolvedTenantTemplateMap) {
        this.resolvedTenantTemplateMap = resolvedTenantTemplateMap;
    }

    public PE getResolvedPropertiesEntity() {
        return resolvedPropertiesEntity;
    }

    private void setResolvedPropertiesEntity(PE resolvedPropertiesEntity) {
        this.resolvedPropertiesEntity = resolvedPropertiesEntity;
    }

    public Map<String, Map<String, PE>> getResolvedPropertiesEntityTenantMap() {
        return resolvedPropertiesEntityTenantMap;
    }

    private void setResolvedPropertiesEntityTenantMap(Map<String, Map<String, PE>> resolvedPropertiesEntityTenantMap) {
        this.resolvedPropertiesEntityTenantMap = resolvedPropertiesEntityTenantMap;
    }

    public Map<String, T> getResolvedTemplateMap() {
        return resolvedTemplateMap;
    }

    private void setResolvedTemplateMap(Map<String, T> resolvedTemplateMap) {
        this.resolvedTemplateMap = resolvedTemplateMap;
    }

    private Boolean lenientFallback = false;


    private Boolean getLenientFallback() {
        return lenientFallback;
    }

    public void setLenientFallback(Boolean lenientFallback) {
        this.lenientFallback = lenientFallback;
    }

    @Nullable
    public String resolveSpecifiedLookupKey(String lookupKey) {
        return lookupKey;
    }

    @Nullable
    public T determineTargetEntity() {
        Assert.notNull(this.resolvedTemplateMap, "Template router not initialized");
        String lookupKey = determineCurrentLookupKey();
        T template = this.resolvedTemplateMap.get(lookupKey);
        if (template == null && (this.lenientFallback || lookupKey == null)) {
            template = this.resolvedTemplate;
        }
        if (template == null) {
            throw new IllegalStateException("Cannot determine target Template for lookup key [" + lookupKey + "]");
        }
        return template;
    }

    /**
     * 获取租户下默认数据源
     *
     * @return
     */
    @Nullable
    public T tenantDefaultTemplate() {
        String tenantName = TenantContextHolder.get();
        if (StringUtil.StringUtilsBylang3.isEmpty(tenantName)) {
            tenantName = this.getResolvedTenantName();
        }

        return this.getResolvedTenantTemplateMap().get(tenantName);
    }


    @Nullable
    public Object resolveSpecifiedEntity(Object entity) {
        return entity;
    }

    @Override
    public void afterPropertiesSet() throws Exception {

        if (this.getDefaultProperties() == null) {
            throw new IllegalArgumentException("Property 'defaultProperties' is required");

        }

        this.resolvedProperties = this.defaultProperties;

        if (this.getDefaultTargetPropertiesEntity() == null) {
            throw new IllegalArgumentException("Property 'defaultTargetPropertiesEntity' is required");
        }

        this.resolvedPropertiesEntity = this.getDefaultTargetPropertiesEntity();

        if (this.getDefaultTargetTemplate() == null) {
            throw new IllegalArgumentException("Property 'defaultTargetTemplate' is required");
        }

        this.resolvedTemplate = this.getDefaultTargetTemplate();

        if (this.getDefaultDataSourceTenantNameWiteMainNameMap() == null
                || this.getDefaultDataSourceTenantNameWiteMainNameMap().isEmpty()) {
            throw new IllegalArgumentException("Property 'defaultDataSourceTenantNameWiteMainNameMap' is required");
        }

        this.resolvedDataSourceTenantNameWiteMainNameMap = this.getDefaultDataSourceTenantNameWiteMainNameMap();

        if (this.getDefaultTenantName() == null) {
            throw new IllegalArgumentException("Property 'defaultTenantName' is required");
        }

        this.resolvedTenantName = this.getDefaultTenantName();

        if (this.getDefaultDataSourcetName() == null) {
            throw new IllegalArgumentException("Property 'defaultDataSourcetName' is required");
        }

        this.resolvedDataSourcetName = this.getDefaultDataSourcetName();


        if (this.getDefaultTargetPropertiesEntityTenantMap() == null) {
            throw new IllegalArgumentException("Property 'defaultTargetPropertiesEntityTenantMap' is required");
        }

        this.resolvedPropertiesEntityTenantMap = this.getDefaultTargetPropertiesEntityTenantMap();

        if (this.getDefaultTargetTemplateTenantMap() == null) {
            throw new IllegalArgumentException("Property 'defaultTargetTemplateTenantMap' is required");
        }

        this.resolvedTemplateTenantMap = this.getDefaultTargetTemplateTenantMap();

        if (this.getDefaultTenantTemplateMap() == null) {
            throw new IllegalArgumentException("Property 'defaultTargetTemplateMap' is required");
        }

        this.resolvedTenantTemplateMap = this.getDefaultTenantTemplateMap();

        if (this.getDefaultTargetTemplateMap() == null) {
            throw new IllegalArgumentException("Property 'defaultTargetTemplateMap' is required");
        }

        this.resolvedTemplateMap = CollectionUtils.newHashMap(this.getDefaultTargetTemplateMap().size());

        this.getDefaultTargetTemplateMap().forEach((key, value) -> {
            String lookupKey = resolveSpecifiedLookupKey(key);
            T template = (T) resolveSpecifiedEntity(value);
            this.resolvedTemplateMap.put(lookupKey, template);
        });

    }


    /**
     * @return
     */
    @Nullable
    protected String determineCurrentLookupKey() {
        DataSourceTypeEnum dataSourceTypeEnum = this.getResolvedProperties().getDataSourceTypeEnum();

        String tenantName = TenantContextHolder.get();
        if (StringUtil.StringUtilsBylang3.isEmpty(tenantName)) {
            tenantName = this.getResolvedTenantName();
        }

        String dataSourceMainName = DataSourceContextHolder.getDataSourceName(dataSourceTypeEnum);

        if (StringUtil.StringUtilsBylang3.isEmpty(dataSourceMainName)) {
            dataSourceMainName = this.getResolvedDataSourcetName();
        }
        return tenantName.concat("_").concat(dataSourceMainName);
    }


    /**
     * 路由数据生成
     *
     * @param properties            数据主配置
     * @param dataSourceTemplateCut 数据处理切面
     */
    public void routing(P properties, DataSourceTemplateCut<T, PE> dataSourceTemplateCut) {

        Tenant tenant = SpringUtil.getBean(Tenant.class);
        if (ObjectUtil.ObjectUtilByLang3.isEmpty(tenant)) {
            log.error("无法获取默认租户配置");
            throw new BadConfigException(BadException.bulidErrorCode(ExceptionEnum.COMMON_DATASOURCE, "013"),
                    "数据源类型不能为空");
        }


        //配置文件进行校验
        properties.checkAndResolve(tenant);

        this.setDefaultProperties(properties);

        /**
         * 拿到放置的配置文件
         */
        P defaultProperties = this.getDefaultProperties();

        //设置默认的租户名称
        this.setDefaultTenantName(tenant.getDefaultTenantName());

        //设置默认数据源名称
        this.setDefaultDataSourcetName(defaultProperties.getDefaultMainDataSourceName());

        defaultProperties.getAllPropertiesTenantEntityMap().keySet().stream().forEach(tenantName -> {
            PropertiesTenantEntity<PE> propertiesTenantEntity = defaultProperties.getAllPropertiesTenantEntityMap().get(tenantName);
            //当前租户下主数据源名称
            String mainDataSourceName = propertiesTenantEntity.getMainDataSourceName();

            this.getDefaultDataSourceTenantNameWiteMainNameMap().put(tenantName, mainDataSourceName);
            //该租户下，所有的数据源配置
            Map<String, PE> allPropertiesEntityMap = propertiesTenantEntity.getAllPropertiesEntityMap();

            //租户对数据源名称对配置文件
            this.getDefaultTargetPropertiesEntityTenantMap().put(tenantName, allPropertiesEntityMap);

            //租户对数据源名称对服务类
            Map<String, T> templateMap = new HashMap<>();
            this.getDefaultTargetTemplateTenantMap().put(tenantName, templateMap);

            allPropertiesEntityMap.keySet().stream().forEach(dataSourceName -> {
                PE propertiesEntity = allPropertiesEntityMap.get(dataSourceName);
                T template = dataSourceTemplateCut.cut(tenantName, dataSourceName, propertiesEntity);
                templateMap.put(dataSourceName, template);
                this.getDefaultTargetTemplateMap().put(tenantName.concat("_").concat(dataSourceName), template);
                if (mainDataSourceName.equals(dataSourceName)) {
                    this.getDefaultTenantTemplateMap().put(tenantName, template);
                }
            });
        });


        //获取主租户下的主数据源配置类
        Map<String, PE> propertiesEntityMap = this.getDefaultTargetPropertiesEntityTenantMap().get(this.getDefaultTenantName());
        PE propertiesEntity = propertiesEntityMap.get(this.getDefaultDataSourcetName());
        this.setDefaultTargetPropertiesEntity(propertiesEntity);

        //获取主租户下的主数据源服务类
        Map<String, T> templateMap = this.getDefaultTargetTemplateTenantMap().get(this.getDefaultTenantName());
        T template = templateMap.get(this.getDefaultDataSourcetName());
        this.setDefaultTargetTemplate(template);
    }

    @Override
    public void destroy() {
        this.getResolvedTemplateMap().values().stream().forEach(template -> template.destroy());
    }
}
